<script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue";
import type { FormRules } from "px-ui";
import { cloneDeep } from "@pureadmin/utils";
import searchButtons from "./searchButtons.vue";
import type { SelectDataType, TreeDataType } from "@/api/type";
import { queryChildByTypeCodeAndItemValue } from "@/api/solidity";

const loading = defineModel("loading", { type: Boolean, default: false });

interface Props {
  /**  每个输入项之间的间距，同 px-row gutter */
  gutter?: number;
  /**  每横排展示的输入项数量（注意：是输入项，不包括按钮） */
  rowNum?: 2 | 3 | 4 | 5 | 6;
  /**  按钮的span */
  operationSpan?: 2 | 3 | 4 | 5 | 6;
  /**  输入项配置 */
  option?: Array<CommonSearch.SearchOption>;
  /**  查询时 忽略的字段 数组类型 */
  ignoreKeys?: Array<string>;
  /**  表单校验，同 px-form rules */
  rules?: CommonSearch.SearchFormRulesType;
  /**  是否初始查询 默认为true */
  immediate?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  gutter: 16,
  rowNum: 4,
  immediate: true
});

interface Emits {
  /**  查询事件触发 */
  (event: "submit", form: any, flag?: boolean): void;
  /**  重置事件触发 */
  (event: "reset", form: any): void;
}

const emits = defineEmits<Emits>();

const searchFormRef = ref<CommonSearch.SearchFormRefType>(null);
const commonSearchForm = reactive<Record<string, any>>({});
const selectOptions = reactive<Record<string, SelectDataType>>({});
const treeSelectData = reactive<Record<string, TreeDataType>>({});
const defaultTime = ref<Array<Date>>([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]);
const selectLoading = ref<boolean>(false);

const searchRules = computed<InstanceType<typeof FormRules>>(() => {
  if (props.rules && Object.keys(props.rules)?.length) {
    return props.rules;
  } else {
    let rules: InstanceType<typeof FormRules> = {};
    for (const item of props.option) {
      const { rule = null, required = false, key = "", validateMassage = "", placeholder = "" } = item;
      if (rule) {
        rules[key] = rule;
      } else if (required) {
        rules[key] = [{ required, message: validateMassage || placeholder, trigger: "blur" }];
      }
    }
    return rules;
  }
});

const span = computed<number>(() => {
  let defaultSpan = 0;
  switch (props.rowNum) {
    case 2:
      defaultSpan = 10;
      break;
    case 3:
      defaultSpan = 6;
      break;
    case 4:
      defaultSpan = 5;
      break;
    case 5:
      defaultSpan = 4;
      break;
    case 6:
      defaultSpan = props.option.length === props.rowNum ? 3 : 4;
      break;
    default:
      defaultSpan = 4;
      break;
  }
  return props.option.length > props.rowNum ? Math.floor(24 / props.rowNum) : defaultSpan;
});

const operationSpan = computed<number>(() => {
  if (props.operationSpan) {
    return props.operationSpan;
  }
  if (props.option.length > props.rowNum) {
    const cl = (props.option.length % props.rowNum) * span.value;
    if (24 - cl > 2) {
      return 24 - cl || 3;
    } else {
      return span.value || 3;
    }
  } else {
    const cl = 24 - props.option.length * span.value;
    if (cl > 2) {
      return cl || 3;
    } else {
      return 24 - props.rowNum * span.value || 3;
    }
  }
});

const formatItemSpan = (item: CommonSearch.SearchOption): number => {
  const { type = "", span: span_ = 0 } = item;
  if (span_) return span_;
  if (type === "daterange" || type === "datetimerange") {
    return span.value * 2;
  }
  return span.value;
};

const getRealSelectOptions = async (): Promise<undefined> => {
  loading.value = true;
  if (!props.option?.length) return;
  for (const item of props.option) {
    const {
      axiosApi = null,
      params = null,
      option = [],
      formatOption = null,
      mapOption = null,
      filterOption = null,
      key = "",
      typeCode = "",
      type = "",
      remote = false,
      relationDefault = false,
      relation = "",
      relationApi = null
    } = item;
    if ((type !== "select" && type !== "tree-select") || (!axiosApi && !option)) continue;
    if (option?.length) {
      selectOptions[key] = formatOption ? formatOption(option) : mapOption ? option.map(mapOption) : option;
      if (filterOption && typeof filterOption === "function") {
        selectOptions[key] = selectOptions[key].filter(filterOption);
      }
      continue;
    }
    if (axiosApi && !remote) {
      try {
        const res = await axiosApi(params);
        if (res?.length) {
          if (type === "select") {
            selectOptions[key] = formatOption
              ? formatOption(res)
              : mapOption
                ? res.map(mapOption)
                : res.map(item => ({ ...item, label: item.name, value: item.id }));
            if (filterOption && typeof filterOption === "function") {
              selectOptions[key] = selectOptions[key].filter(filterOption);
            }
          } else if (type === "tree-select") {
            treeSelectData[key] = formatOption ? formatOption(res) : mapOption ? res.map(mapOption) : res.map(item => item);
          }
        } else {
          if (type === "select") {
            selectOptions[key] = [];
          } else if (type === "tree-select") {
            treeSelectData[key] = [];
          }
        }
      } catch (error) {
        console.error(error);
      }
      continue;
    }
    if (relation && !relationApi) {
      const item = props.option.find(item => item.key === relation);
      if (item.value) {
        await selectChangeHandler(item.value as string, item);
        continue;
      } else if (relationDefault) {
        await selectChangeHandler("", item);
        continue;
      }
    }
    if (typeCode) {
      try {
        let params = null;
        if (typeof typeCode === "string") {
          params = { typeCode };
        } else if (Object.prototype.toString.call(typeCode) === "[object Object]" && "typeCode" in typeCode) {
          params = { ...typeCode };
        }
        const res: Array<any> = await queryChildByTypeCodeAndItemValue(params);
        if (res?.length) {
          selectOptions[key] = res.map(item => ({ ...item, label: item.itemName, value: item.itemValue }));
        } else {
          selectOptions[key] = [];
        }
      } catch (error) {
        console.error(error);
      }
      continue;
    }
    selectOptions[key] = [];
    continue;
  }
  loading.value = false;
};

const selectChangeHandler = async (val: string | number | boolean | object, item: any): Promise<undefined> => {
  const {
    key = "",
    value = null,
    relation = "",
    relationType = "",
    relationApi = null,
    getParams = null,
    formatOption = null,
    relationMapOption = null,
    relationFilterOption = null,
    mapReturnSearchParams = null
  } = item;
  if (!val && typeof val !== "boolean" && typeof val !== "number") commonSearchForm[key] = value;
  if (mapReturnSearchParams && typeof mapReturnSearchParams === "function") {
    const item = selectOptions[key].find(item => item.value === val);
    if (item) {
      const ag = mapReturnSearchParams(item);
      for (const key in ag) {
        commonSearchForm[key] = ag[key];
      }
    }
  }
  if (!relation) return;
  if (!relationApi) return;
  treeSelectData[relation] = [];
  selectOptions[relation] = [];
  commonSearchForm[relation] = "";
  try {
    const res = await relationApi(getParams ? getParams(val) : null);
    if (relationType === "tree-select") {
      if (res?.length) {
        treeSelectData[relation] = res;
      }
    } else {
      if (res?.length) {
        selectOptions[relation] = formatOption
          ? formatOption(res)
          : relationMapOption
            ? res.map(relationMapOption)
            : res.map(item => ({ ...item, label: item.name, value: item.id }));
        if (relationFilterOption && typeof relationFilterOption === "function") {
          selectOptions[relation] = selectOptions[relation].filter(relationFilterOption);
        }
      }
    }
  } catch (error) {
    console.error(error);
  }
};

const selectRemoteMethod = async (query: string = "", item: Record<string, any> = {}): Promise<void> => {
  const { key = "", selectRemoteApi = null, getParams = null, formatOption = null, mapOption = null, filterOption = null } = item;
  selectOptions[key] = [];
  if (query) {
    selectLoading.value = true;
    const res = await selectRemoteApi(getParams ? getParams(query) : null);
    if (res) {
      selectOptions[key] = formatOption
        ? formatOption(res)
        : mapOption
          ? res.map(mapOption)
          : res.map(item => ({ ...item, label: item.name, value: item.id }));
      if (filterOption && typeof filterOption === "function") {
        selectOptions[key] = selectOptions[key].filter(filterOption);
      }
    }
    selectLoading.value = false;
  }
};

/**
 * @description: 初始化
 * @param {*} f  是否重置默认初始值 defaultValue
 * @return {*}
 */
const initSearchForm = async (f?: boolean): Promise<undefined> => {
  if (!props.option?.length) return;
  for (const item of props.option) {
    const { key, type, value, defaultValue, remote, relation, relationApi } = item;
    let v1 = null;
    let v2 = null;
    switch (type) {
      case "daterange":
      case "datetimerange":
        v1 = value || value === null ? value : [];
        v2 = defaultValue || v1;
        commonSearchForm[key] = f ? v1 : v2;
        break;
      case "select":
        v1 = value || value === null || value === 0 ? value : "";
        v2 = defaultValue || v1;
        commonSearchForm[key] = f ? v1 : v2;
        if (value || defaultValue) {
          if (remote) {
            await selectRemoteMethod(commonSearchForm[key] as string, item);
            await selectChangeHandler(commonSearchForm[key], item);
          }
          if (relation && relationApi) {
            await selectChangeHandler(commonSearchForm[key], item);
          }
        }
        break;
      default:
        v1 = value || value === null || value === 0 ? value : "";
        v2 = defaultValue || v1;
        commonSearchForm[key] = f ? v1 : v2;
        break;
    }
  }
};

const getFormParams = (): Record<string, any> => {
  function mapParams<T extends Record<string, any>>(p: T) {
    return props.option.reduce((pre: Record<string, any>, cur: CommonSearch.SearchOption): Record<string, any> => {
      const { type = "", key = "", realKeys = null } = cur;
      if (Array.isArray(realKeys) && realKeys?.length) {
        if (type === "datetimerange" || type === "daterange") {
          pre[realKeys[0]] = p[key]?.length ? p[key][0] : "";
          pre[realKeys[1]] = p[key]?.length ? p[key][1] : "";
        } else {
          for (const key of realKeys) {
            pre[key] = p[key];
          }
        }
      } else {
        pre[key] = p[key];
      }
      return pre;
    }, {});
  }
  if (props.ignoreKeys?.length) {
    const form = {};
    for (const key in commonSearchForm) {
      if (!props.ignoreKeys.includes(key)) {
        form[key] = commonSearchForm[key];
      }
    }
    return mapParams(cloneDeep(form));
  } else {
    return mapParams(cloneDeep(commonSearchForm));
  }
};

const submitHandler = (flag: boolean = false): undefined => {
  const form = getFormParams();
  emits("submit", form, flag);
};

const resetHandler = async (): Promise<void> => {
  await getRealSelectOptions();
  await initSearchForm(true);
  searchFormRef.value.validate(valid => {
    if (valid) {
      const form = getFormParams();
      emits("reset", form);
    }
  });
};

const reset = (): undefined => {
  if (!searchFormRef.value) return;
  searchFormRef.value.resetFields();
  searchFormRef.value.clearValidate();
  initSearchForm(true);
};

onMounted(async () => {
  await getRealSelectOptions();
  await initSearchForm();
  props.immediate && submitHandler(true);
});

defineExpose({
  el: searchFormRef,
  initSearchForm,
  reset
});
</script>

<template>
  <px-form ref="searchFormRef" class="common-search-form" :model="commonSearchForm" :rules="searchRules">
    <px-row :gutter="gutter">
      <px-col v-for="item in option" :key="item.key" :span="formatItemSpan(item)">
        <px-form-item v-if="item.type === 'input'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-input v-model.trim="commonSearchForm[item.key]" :placeholder="item.placeholder" clearable v-bind="item.props" />
        </px-form-item>

        <px-form-item v-if="item.type === 'select'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-select
            v-if="item.relation"
            v-model="commonSearchForm[item.key]"
            :placeholder="item.placeholder"
            clearable
            filterable
            v-bind="item.props"
            @change="selectChangeHandler($event, item)"
          >
            <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
          </px-select>
          <px-select
            v-else-if="item.remote"
            v-model="commonSearchForm[item.key]"
            clearable
            filterable
            remote
            reserve-keyword
            remote-show-suffix
            :placeholder="item.placeholder"
            :remote-method="q => selectRemoteMethod(q, item)"
            :loading="selectLoading"
            v-bind="item.props"
            @change="selectChangeHandler($event, item)"
          >
            <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
          </px-select>
          <px-select
            v-else
            v-model="commonSearchForm[item.key]"
            :placeholder="item.placeholder"
            clearable
            filterable
            v-bind="item.props"
            @change="selectChangeHandler($event, item)"
          >
            <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
          </px-select>
        </px-form-item>

        <px-form-item v-if="item.type === 'tree-select'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-tree-select
            v-model="commonSearchForm[item.key]"
            clearable
            filterable
            check-strictly
            highlight-current
            :node-key="`${item.nodeKey || 'id'}`"
            :props="item.treeSelectProps"
            :data="item.relation || item.axiosApi ? treeSelectData[item.key] : item.data"
            :render-after-expand="false"
            :placeholder="item.placeholder"
            v-bind="item.props"
          />
        </px-form-item>

        <px-form-item v-if="item.type === 'date'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-date-picker
            v-model="commonSearchForm[item.key]"
            type="date"
            :editable="false"
            :placeholder="item.placeholder"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            v-bind="item.props"
          />
        </px-form-item>

        <px-form-item v-if="item.type === 'daterange'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-date-picker
            v-model="commonSearchForm[item.key]"
            type="daterange"
            :editable="false"
            range-separator="-"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD HH:mm:ss"
            :default-time="defaultTime"
            v-bind="item.props"
          />
        </px-form-item>

        <px-form-item v-if="item.type === 'datetimerange'" :label="item.label || ''" :prop="item.key" :required="!!item?.required">
          <px-date-picker
            v-model="commonSearchForm[item.key]"
            type="datetimerange"
            :editable="false"
            range-separator="-"
            :prefix-icon="null"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
            format="YYYY-MM-DD HH:mm:ss"
            value-format="YYYY-MM-DD HH:mm:ss"
            :default-time="defaultTime"
            v-bind="item.props"
          />
        </px-form-item>
      </px-col>
      <px-col :span="operationSpan">
        <px-form-item class="search-btn-form-item">
          <searchButtons :formRef="searchFormRef" @submit="submitHandler" @reset="resetHandler">
            <slot name="button" />
          </searchButtons>
        </px-form-item>
      </px-col>
    </px-row>
  </px-form>
</template>

<style lang="scss" scoped>
.common-search-form {
  gap: 16px;

  .px-row {
    row-gap: 8px;
    margin-right: 0 !important;
  }

  :deep(.px-form-item) {
    margin-bottom: 0;

    &:not(.search-btn-form-item) {
      background-color: #f2f2f2;
      border: 1px solid #dfdfdf;
      border-radius: 4px;
    }

    .px-form-item__label {
      padding: 0 14px;
      font-weight: 500;
      color: #555;
    }

    .px-form-item__content {
      .px-input,
      .px-select,
      .px-date-picker,
      .px-time-picker,
      .px-tree-select {
        width: 100%;
        height: 100%;

        .select-trigger {
          height: 100%;
        }
      }

      .px-input__wrapper {
        border: none;

        &.is-focus {
          border: none;
          box-shadow: none;
        }
      }

      .px-input__icon.px-range__icon,
      .px-input__prefix {
        display: none;
      }

      .px-date-editor .px-range-input {
        width: 46%;
      }
    }
  }
}
</style>
